/*
 * Copyright (C) 2004-2006 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: ui.c,v 1.50 2006/04/08 21:34:50 dsalt Exp $
 *
 * various UI bits:
 * - button registry and update (state display etc.)
 * - slider/spinner adjustment creation and update
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <pthread.h>

#include "ui.h"
#include "utils.h"
#include "engine.h"
#include "player.h"
#include "playlist.h"
#include "preferences.h"
#include "gtkvideo.h"

/*
 * Playback status
 * UI button & adjustment handling
 */

gboolean no_recursion = FALSE;

gboolean fs_toolbar_at_top = FALSE;
gboolean fs_toolbar_visible = FALSE;
gboolean wm_toolbar_visible = TRUE;
gboolean wm_toolbar_snap = TRUE;

GtkIconSize icon_size_logo;

static ui_status_t status = UI_CURRENT_STATE;
pthread_mutex_t status_lock = PTHREAD_MUTEX_INITIALIZER;

static GSList *c_buttons[Control_Buttons] = { NULL };
static GtkObject *c_adjustments[Control_Adjustments] = { NULL };

/* (Internal) Reflect play/pause/stop status in control buttons */

static void ui_set_list (int index, const char *property, gboolean state)
{
  GSList *btn;
  foreach_glist (btn, c_buttons[index])
    g_object_set (G_OBJECT (btn->data), property, state, NULL);
}

static void ui_int_set_status (ui_status_t status)
{
  char states[Control_PlayerButtons] = { 0 };
  int live = player_live_stream () || playlist_showing_logo ();
  int i;

  switch (status)
  {
  case UI_STOP:		states[Control_STOP]	= 1; break;
  case UI_PAUSE:	states[Control_PAUSE]	= 1; break;
  case UI_PLAY_SLOW:	states[Control_PLAY]	= 1;
			states[Control_PAUSE]	= 1; break;
  case UI_PLAY:		states[Control_PLAY]	= 1; break;
  case UI_FAST_FORWARD:	states[Control_FASTFWD]	= 1; break;
  default:;
  }

  no_recursion = TRUE;
  for (i = 0; i < Control_PlayerButtons; ++i)
  {
    ui_set_list (i, "active", states[i]);
    if (i == Control_PLAY)
      ui_set_list (i, "sensitive", !!playlist_size ());
    else if (i == Control_PAUSE || i == Control_REWIND || i == Control_FASTFWD)
      ui_set_list (i, "sensitive", !live);
  }
  no_recursion = FALSE;
}

/* (Internal) Reflect mute status in control button */

static void ui_int_set_mute (int mute)
{
  GSList *btn;
  no_recursion = TRUE;
  foreach_glist (btn, c_buttons[Control_MUTE])
    g_object_set (G_OBJECT(btn->data), "active", !mute, NULL);
  no_recursion = FALSE;
}

/* Set/show play/pause/stop/mute status in control buttons */

void ui_set_status (ui_status_t newstatus)
{
  pthread_mutex_lock (&status_lock);


  switch (newstatus)
  {
  case UI_CURRENT_STATE:
    ui_int_set_status (status);
    break;
  case UI_STOP:
  case UI_PAUSE:
  case UI_PLAY_SLOW:
  case UI_PLAY:
  case UI_FAST_FORWARD:
    if (playlist_showing_logo ())
      newstatus = UI_STOP;
    ui_int_set_status (status = newstatus);
    break;
  case UI_AUDIO_UNMUTE:	ui_int_set_mute (FALSE); break;
  case UI_AUDIO_MUTE:	ui_int_set_mute (TRUE); break;
  case UI_FS_TOOLBAR:	/* nothing to do */; break;
  case UI_FS_TOOLBAR_POS:/* nothing to do */; break;
  case UI_WM_TOOLBAR:	/* nothing to do */; break;
  case UI_WM_TOOLBAR_SNAP:/* nothing to do */; break;
  default:;
  }

  pthread_mutex_unlock (&status_lock);
}

/* Register a button for the above status functions */

void ui_register_control_button (control_button_t item, GtkWidget *widget)
{
  g_return_if_fail (item < Control_Buttons);
  c_buttons[item] = g_slist_append (c_buttons[item], widget);
}

/* Adjustment object <=> adjustment type <=> JS object */

typedef struct {
  gdouble start, min, max, step, page, pagesize;
  const char *setting, *jspref;
  int param;
} ui_adjustable_t;

static const ui_adjustable_t ranges[] = {
  [Control_SEEKER]		= {     0,       0,  65535,    1,    10, 0, NULL },
  [Control_AUDIO_CHANNEL]	= {    -1,      -1,     32,    1,     1, 0, NULL,			 "ao_channel",	  XINE_PARAM_AUDIO_CHANNEL_LOGICAL },
  [Control_VOLUME]		= {    50,       0,    100,    1,    10, 0, "audio.volume.mixer_volume", "ao_volume",	  XINE_PARAM_AUDIO_VOLUME },
  [Control_COMPRESSOR]		= {   100,     100,   1000,   10,   100, 0, "gui.ao_compressor",	 "ao_compressor", XINE_PARAM_AUDIO_COMPR_LEVEL },
  [Control_AMPLIFIER]		= {   100,       0,    200,    1,    10, 0, "gui.ao_amplifier",		 "ao_amplifier",  XINE_PARAM_AUDIO_AMP_LEVEL },
  [Control_AV_SYNC]		= {     0, -180000, 180000, 1000, 10000, 0, "gui.av_sync",		 "av_sync",	  XINE_PARAM_AV_OFFSET },
  [Control_HUE]			= { 32768,       0,  65535,  100,  1000, 0, "gui.vo_hue",		 "vo_hue",	  XINE_PARAM_VO_HUE },
  [Control_SATURATION]		= { 32768,       0,  65535,  100,  1000, 0, "gui.vo_saturation",	 "vo_saturation", XINE_PARAM_VO_SATURATION },
  [Control_CONTRAST]		= { 32768,       0,  65535,  100,  1000, 0, "gui.vo_contrast",		 "vo_contrast",   XINE_PARAM_VO_CONTRAST },
  [Control_BRIGHTNESS]		= { 32768,       0,  65535,  100,  1000, 0, "gui.vo_brightness",	 "vo_brightness", XINE_PARAM_VO_BRIGHTNESS },
  [Control_EQ_30]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_30",		 "eq_30",	  XINE_PARAM_EQ_30HZ },
  [Control_EQ_60]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_60",		 "eq_60",	  XINE_PARAM_EQ_60HZ },
  [Control_EQ_125]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_125",		 "eq_125",	  XINE_PARAM_EQ_125HZ },
  [Control_EQ_250]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_250",		 "eq_250",	  XINE_PARAM_EQ_250HZ },
  [Control_EQ_500]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_500",		 "eq_500",	  XINE_PARAM_EQ_500HZ },
  [Control_EQ_1K]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_1K",		 "eq_1K",	  XINE_PARAM_EQ_1000HZ },
  [Control_EQ_2K]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_2K",		 "eq_2K",	  XINE_PARAM_EQ_2000HZ },
  [Control_EQ_4K]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_4K",		 "eq_4K",	  XINE_PARAM_EQ_4000HZ },
  [Control_EQ_8K]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_8K",		 "eq_8K",	  XINE_PARAM_EQ_8000HZ },
  [Control_EQ_16K]		= {   100,	 0,    100,    1,    10, 0, "gui.eq_16K",		 "eq_16K",	  XINE_PARAM_EQ_16000HZ },
};
static gdouble starts[G_N_ELEMENTS (ranges)];
static gdouble inits[G_N_ELEMENTS (ranges)];
static se_o_t *jsobjs[G_N_ELEMENTS (ranges)];

/* true if not volume setting or audio.remember_volume is set */
static int check_remember_volume (const ui_adjustable_t *info)
{
  xine_cfg_entry_t entry;
  return info->param != XINE_PARAM_AUDIO_VOLUME ||
         !xine_config_lookup_entry (xine, "audio.volume.remember_volume", &entry) ||
         entry.num_value;
}

static void ui_adjustment_value_changed_cb (GtkAdjustment *adj,
					    const ui_adjustable_t *info)
{
  xine_cfg_entry_t entry;

  if (no_recursion)
    return;

  if (info->param)
    xine_set_param (stream, info->param, adj->value);

  if (info->param == XINE_PARAM_AUDIO_VOLUME)
    return;
  if (info->setting &&
      xine_config_lookup_entry (xine, info->setting, &entry))
  {
    entry.num_value = adj->value;
    preferences_update_entry (&entry);
  }
}

static void
ui_seeker_value_changed_cb (GtkAdjustment *seeker, gpointer data)
{
  if (!no_recursion &&
      xine_get_stream_info (stream, XINE_STREAM_INFO_SEEKABLE))
  {
    int speed;
    ++no_recursion;
    speed = xine_get_param (stream, XINE_PARAM_SPEED);
    xine_play (stream, (gint) seeker->value, 0);
    xine_set_param (stream, XINE_PARAM_SPEED, speed);
    --no_recursion;
  }
}

GtkObject *ui_register_control_adjustment (control_adjustment_t item)
{
  g_return_val_if_fail (item < Control_Adjustments, NULL);

  if (!c_adjustments[item])
  {
    xine_cfg_entry_t entry;
    int start = ranges[item].start;

    if (ranges[item].setting)
    {
      if (!check_remember_volume (&ranges[item]))
        start = xine_get_param (stream, XINE_PARAM_AUDIO_VOLUME);
      else if (xine_config_lookup_entry (xine, ranges[item].setting, &entry))
	start = entry.num_value;
    }

    starts[item] = start;

    if (ranges[item].param)
      inits[item] = start = xine_get_param (stream, ranges[item].param);

    c_adjustments[item] =
      gtk_adjustment_new (start, ranges[item].min, ranges[item].max,
			  ranges[item].step, ranges[item].page,
			  ranges[item].pagesize);
    if (ranges[item].param || ranges[item].setting)
      g_signal_connect (c_adjustments[item], "value-changed",
			G_CALLBACK(ui_adjustment_value_changed_cb),
			(gpointer) &ranges[item]);
    else if (item == Control_SEEKER) /* special case */
      g_signal_connect (c_adjustments[item], "value-changed",
			G_CALLBACK (ui_seeker_value_changed_cb), NULL);
  }
  return c_adjustments[item];
}

/* Set the state of every button in a class, e.g. every play button */

void ui_set_control_button (control_button_t item, gboolean state)
{
  GSList *btn;
  g_return_if_fail (item >= Control_PlayerButtons && item < Control_Buttons);
  no_recursion = TRUE;
  if (c_buttons[item] &&
      (GTK_TOGGLE_BUTTON(c_buttons[item]->data))->active != state)
    foreach_glist (btn, c_buttons[item])
      g_object_set (G_OBJECT(btn->data), "active", state, NULL);
  no_recursion = FALSE;
}

/* Set the value of an adjustment */

void ui_set_control_adjustment (control_adjustment_t item, gdouble value)
{
  g_return_if_fail (item < Control_Adjustments);
  no_recursion = TRUE;
  if ((GTK_ADJUSTMENT(c_adjustments[item]))->value != value)
    g_object_set (G_OBJECT(c_adjustments[item]), "value", value, NULL);
  no_recursion = FALSE;
}

void ui_reset_control_adjustment (control_adjustment_t item)
{
  g_return_if_fail (item < Control_Adjustments);
  g_object_set (G_OBJECT(c_adjustments[item]), "value",
		ranges[item].start, NULL);
}

void ui_revert_control_adjustment (control_adjustment_t item)
{
  g_return_if_fail (item < Control_Adjustments);
  g_object_set (G_OBJECT(c_adjustments[item]), "value", starts[item], NULL);
}

void ui_clear_control_adjustment (control_adjustment_t item)
{
  g_return_if_fail (item < Control_Adjustments);
  if (check_remember_volume (&ranges[item]))
    xine_set_param (stream, ranges[item].param, inits[item]);
}

/* Update all sliders & spinners which use this adjustment */

void ui_xine_set_param_from_adjustment (control_adjustment_t item)
{
  g_return_if_fail (item < Control_Adjustments);
  gtk_adjustment_value_changed (GTK_ADJUSTMENT(c_adjustments[item]));
}

/* Button creation */

static GtkWidget *ui_container_new_stock (GtkWidget *box, const char *stock)
{
  GtkWidget *img = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_BUTTON);
  gtk_misc_set_padding (GTK_MISC(img), 0, 0);
  if (box)
    gtk_container_add (GTK_CONTAINER(box), img);
  return box ? box : img;
}

GtkWidget *ui_button_new_stock (const char *stock)
{
  return ui_container_new_stock (gtk_button_new (), stock);
}

GtkWidget *ui_toggle_button_new_stock (const char *stock)
{
  return ui_container_new_stock (gtk_toggle_button_new (), stock);
}

GtkWidget *ui_button_new_stock_mnemonic (const char *stock,
					 const char *mnemonic)
{
  GtkWidget *button = gtk_button_new ();
  GtkWidget *align = gtk_alignment_new (0.5, 0.5, 0, 0);
  GtkWidget *hbox = gtk_hbox_new (FALSE, 2);
  /* hbox in alignment in button => hbox is not expanded to fill the button */
  gtk_container_add (GTK_CONTAINER(align), hbox);
  gtk_container_add (GTK_CONTAINER(button), align);
  gtk_box_pack_start (GTK_BOX(hbox), ui_container_new_stock (NULL, stock),
		      FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX(hbox), gtk_label_new_with_mnemonic (mnemonic),
		      FALSE, FALSE, 0);
  return button;
}

static GtkWidget *ui_container_new_icon_name (GtkWidget *box, const char *name)
{
  GtkWidget *img = gtk_image_new_from_icon_name (name, GTK_ICON_SIZE_BUTTON);
  gtk_misc_set_padding (GTK_MISC(img), 0, 0);
  if (box)
    gtk_container_add (GTK_CONTAINER(box), img);
  return box ? box : img;
}

GtkWidget *ui_button_new_icon_name (const char *name)
{
  return ui_container_new_icon_name (gtk_button_new (), name);
}

GtkWidget *ui_toggle_button_new_icon_name (const char *name)
{
  return ui_container_new_icon_name (gtk_toggle_button_new (), name);
}

GtkWidget *ui_button_new_icon_name_mnemonic (const char *name,
					     const char *mnemonic)
{
  GtkWidget *button = gtk_button_new ();
  GtkWidget *align = gtk_alignment_new (0.5, 0.5, 0, 0);
  GtkWidget *hbox = gtk_hbox_new (FALSE, 2);
  /* hbox in alignment in button => hbox is not expanded to fill the button */
  gtk_container_add (GTK_CONTAINER(align), hbox);
  gtk_container_add (GTK_CONTAINER(button), align);
  gtk_box_pack_start (GTK_BOX(hbox), ui_container_new_icon_name (NULL, name),
		      FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX(hbox), gtk_label_new_with_mnemonic (mnemonic),
		      FALSE, FALSE, 0);
  return button;
}

/* Menu/toolbar basics */

GtkUIManager *ui_create_manager (const char *label, GtkWidget *window)
{
  GtkUIManager *ui = gtk_ui_manager_new ();
  GtkActionGroup *action = gtk_action_group_new (label);
#ifdef ENABLE_NLS
  gtk_action_group_set_translation_domain (action, PACKAGE);
#endif
  gtk_ui_manager_insert_action_group (ui, action, 0);
  if (window)
    gtk_window_add_accel_group (GTK_WINDOW(window),
				gtk_ui_manager_get_accel_group (ui));
  return ui;
}

void ui_mark_active (GtkUIManager *ui, const char *const items[],
		     gboolean active)
{
  GtkActionGroup *actions = ui_get_action_group (ui);
  int i;
  for (i = 0; items[i]; ++i)
  {
    gtk_action_set_sensitive (gtk_action_group_get_action (actions, items[i]),
			      active);
  }
}

void gtk_action_group_connect_accelerators (GtkActionGroup *actions)
{
  GList *list = gtk_action_group_list_actions (actions);
  g_list_foreach (list, (GFunc) gtk_action_connect_accelerator, NULL);
  g_list_free (list);
}

/* Toolbar control and JS functions */

#define TRIVIAL_GET(what,where) \
  static void get_##what (se_prop_t *prop, se_prop_read_t *value) \
  { \
    value->i = (where); \
  }
#define TRIVIAL_GET_SET(what,where,how) \
  TRIVIAL_GET (what, where) \
  static void set_##what (int v) \
  { \
    if (gtk_toggle_action_get_active (action_items.how) != v) \
      gtk_action_activate (GTK_ACTION (action_items.how)); \
  }

TRIVIAL_GET_SET (fs_toolbar, fs_toolbar_visible, fs_toolbar)
TRIVIAL_GET     (fs_toolbar_top, fs_toolbar_at_top)
TRIVIAL_GET_SET (wm_toolbar, wm_toolbar_visible, wm_toolbar)
TRIVIAL_GET_SET (wm_toolbar_snap, wm_toolbar_snap, wm_toolbar_snap)

static void set_fs_toolbar_top (int v)
{
  if (v)
    gtk_action_activate (action_items.fs_toolbar_pos->next->data); /* top */
  else
    gtk_action_activate (action_items.fs_toolbar_pos->data); /* bottom */
}

#ifdef WITH_DEPRECATED

static JSBool js_fs_toolbar_show (JSContext *cx, JSObject *obj, uintN argc,
				  jsval *argv, jsval *rval)
{
  JSBool show;

  se_log_fncall_deprecated ("toolbar_show");
  se_argc_check_max (1, "toolbar_show");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "toolbar_show");
    JS_ValueToBoolean (cx, argv[0], &show);
  }
  else
    show = !fs_toolbar_visible;

  window_fs_toolbar_show (show);

  return JS_TRUE;
}

static JSBool js_fs_toolbar_position (JSContext *cx, JSObject *obj, uintN argc,
				      jsval *argv, jsval *rval)
{
  JSBool top;

  se_log_fncall_deprecated ("set_toolbar_position");
  se_argc_check_max (1, "set_toolbar_position");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_toolbar_position");
    JS_ValueToBoolean (cx, argv[0], &top);
  }
  else
    top = !fs_toolbar_at_top;

  set_fs_toolbar_top
    (gtk_radio_action_get_current_value (action_items.fs_toolbar_pos->data));

  return JS_TRUE;
}

#endif

/* A/V settings JS methods */

static int ui_lookup_js_obj (const JSObject *obj)
{
  int i;
  for (i = 0; i < (int) G_N_ELEMENTS (ranges); ++i)
    if (jsobjs[i] && jsobjs[i]->obj == obj)
      return i;
  abort (); /* can't happen */
}

static JSBool js_control_revert (JSContext *cx, JSObject *obj, uintN argc,
				 jsval *argv, jsval *rval)
{
  se_log_fncall ("<control>.revert");
  se_argc_check (0, "<control>.revert");
  ui_revert_control_adjustment (ui_lookup_js_obj (obj));
  *rval = JSVAL_VOID;
  return JS_TRUE;
}

static JSBool js_control_reset (JSContext *cx, JSObject *obj, uintN argc,
				jsval *argv, jsval *rval)
{
  se_log_fncall ("<control>.reset");
  se_argc_check (0, "<control>.reset");
  ui_clear_control_adjustment (ui_lookup_js_obj (obj));
  *rval = JSVAL_VOID;
  return JS_TRUE;
}

/* Boolean properties */

int ui_property_clip_int (const ui_property_t *prop, int value)
{
  if (prop->loop)
  {
    while (value < prop->min)
      value += prop->max - prop->min + 1;
    while (value > prop->max)
      value -= prop->max - prop->min + 1;
  }
  else if (value < prop->min)
    value = prop->min;
  else if (value > prop->max)
    value = prop->max;

  return value;
}

static int ui_prop_set_int (void *data, se_t *se, se_o_t *o,
			    se_prop_t *se_prop, se_prop_read_t value)
{
  const ui_property_t *prop = data;
  value.i = ui_property_clip_int (prop, value.i);
  if (prop->set)
    prop->set (value.i);
  else
    xine_set_param (stream, prop->param, value.i);
  return 0;
}

static int ui_prop_set_bool (void *data, se_t *se, se_o_t *o,
			     se_prop_t *se_prop, se_prop_read_t value)
{
  const ui_property_t *prop = data;
  if (prop->set)
    prop->set (!!value.i);
  else
    xine_set_param (stream, prop->param, !!value.i);
  return 0;
}

static JSBool ui_prop_toggle_bool (JSContext *cx, JSObject *obj,
				   uintN argc, jsval *argv, jsval *rval)
{
  se_t *se = (se_t *) JS_GetContextPrivate(cx);
  se_o_t *o = JS_GetPrivate (cx, obj);
  JSBool v = !se_prop_get_bool (se, o, "v");
  *rval = BOOLEAN_TO_JSVAL (v);
  se_prop_set_bool (se, o, "v", v);
  return JS_TRUE;
}

void ui_create_properties (const ui_property_t *prop, se_o_t *parent,
			   se_prop_type_t type)
{
  int i;
  se_prop_cb_t cb = NULL;

  switch (type)
  {
  case SE_TYPE_INT: cb = ui_prop_set_int; break;
  case SE_TYPE_BOOL: cb = ui_prop_set_bool; break;
  default:;
  }

  for (i = 0; prop[i].name; ++i)
  {
    se_o_t *obj =
      se_create_object (gse, parent, prop[i].name, NULL, SE_GROUP_PROPERTIES,
			gettext (prop[i].help));
    if (prop[i].param)
      se_prop_create_xine_param (gse, obj, "v", prop[i].param, type);
    else
    {
      se_prop_create (gse, obj, "v", "", NULL, 0, type, FALSE);
      se_prop_set_reader_func (gse, obj, "v", prop[i].get);
    }

    if (prop[i].listen.cb)
      se_prop_add_listener (gse, obj, "v", prop[i].listen.cb,
			    prop[i].listen.user_data);
    else
      se_prop_add_listener (gse, obj, "v", cb, &prop[i]);

    switch (type)
    {
    case SE_TYPE_INT:
      se_prop_create_int (gse, obj, "min", prop[i].min, TRUE);
      se_prop_create_int (gse, obj, "max", prop[i].max, TRUE);
      break;
    case SE_TYPE_BOOL:
      se_defun (gse, obj, "toggle", ui_prop_toggle_bool, 0, 0,
		SE_GROUP_HIDDEN, NULL, NULL);
      break;
    default:; /* we don't handle string and float */
    }
  }
}

/* Post-plugin config item callbacks */

static void post_deinterlace_plugin_cb (void *data, xine_cfg_entry_t *cfg)
{
  if (gtv)
    gtk_video_set_post_plugins_deinterlace ((GtkVideo *)gtv, cfg->str_value);
}

static void post_plugins_video_cb (void *data, xine_cfg_entry_t *cfg)
{
  if (gtv)
    gtk_video_set_post_plugins_video ((GtkVideo *)gtv, cfg->str_value);
}

static void post_plugins_audio_cb (void *data, xine_cfg_entry_t *cfg)
{
  if (gtv)
    gtk_video_set_post_plugins_audio ((GtkVideo *)gtv, cfg->str_value,
				      audio_port);
}

/* Initialisation */

void ui_preferences_register (xine_t *this)
{
  static const char *experience_labels[] = {
    N_("Beginner"), N_("Advanced"), N_("Expert"),
    N_("Master of the known universe"), NULL
  };
  static const char *tbar_pos_labels[] = {
    N_("Top, hidden"), N_("Bottom, hidden"),
    N_("Top, visible"), N_("Bottom, visible"),
    NULL
  };
  const char **vis_labels, *const *vis_src;
  unsigned int i;

  /* Register the experience level setting */
  xine_config_register_enum
    (this, "gui.experience_level", 0, experience_labels,
     _("Display of configuration settings"),
     _("Controls whether more advanced configuration settings are shown."),
     0, gtk_true, NULL);

  /* Register a few audio/video config items */
  for (i = 0; i < G_N_ELEMENTS (ranges); ++i)
  {
    if (ranges[i].setting && !strncmp (ranges[i].setting, "gui.", 4))
      xine_config_register_range (this, ranges[i].setting, ranges[i].start,
				  ranges[i].min, ranges[i].max, NULL, NULL,
				  40, NULL, NULL);
    starts[i] = inits[i] = ranges[i].param;
  }

  /* Register some front-end widget options */
  xine_config_register_string
    (this, "gui.post_plugins.deinterlace",
     "tvtime:method=LinearBlend,cheap_mode=1,pulldown=none,use_progressive_frame_flag=1",
     _("Deinterlace plugins' names and parameters"),
     _("Format: plugin:arg=value,arg=value,...;plugin:..."),
     40, post_deinterlace_plugin_cb, CONFIG_DATA_NONE);

  xine_config_register_bool
    (this, "gui.post_plugins.deinterlace_enable", 1,
     _("Enable deinterlacing at startup"), NULL,
     30, NULL, NULL);

  xine_config_register_string
    (this, "gui.post_plugins.video", "",
     _("Video post-processing plugins' names and parameters"),
     _("Format: plugin:arg=value,arg=value,...;plugin:..."),
     40, post_plugins_video_cb, CONFIG_DATA_NONE);

  xine_config_register_bool
    (this, "gui.post_plugins.video_enable", 0,
     _("Enable video post-processing at startup"), NULL,
     30, NULL, NULL);

  xine_config_register_string
    (this, "gui.post_plugins.audio", "",
     _("Audio post-processing plugins' names and parameters"),
     _("Format: plugin:arg=value,arg=value,...;plugin:..."),
     40, post_plugins_audio_cb, CONFIG_DATA_NONE);

  xine_config_register_bool
    (this, "gui.post_plugins.audio_enable", 0,
     _("Enable audio post-processing at startup"), NULL,
     30, NULL, NULL);

  vis_src = xine_list_post_plugins_typed
	      (xine, XINE_POST_TYPE_AUDIO_VISUALIZATION);
  for (i = 0; vis_src[i]; ++i) /**/;
  vis_labels = calloc (i + 2, sizeof (char *));
  vis_labels[0] = N_("None"); /* translate for display, not for config file */
  memcpy (vis_labels + 1, vis_src, (i + 1) * sizeof (char *));

  xine_config_register_enum
    (this, "gui.post_plugins.audio_visualisation", 1, vis_labels,
     _("Default audio visualisation plugin"),
     _("Post-plugin to be used when playing streams without video"),
     30, NULL, NULL);

  xine_config_register_bool
    (this, "gui.post_plugins.always_show_logo", 1,
     _("In windowed mode, whether the logo is shown after playing an audio-only stream if no audio visualisation was active."), NULL,
     0, gtk_true, NULL);

  xine_config_register_enum
    (this, "gui.fullscreen_toolbar", 1, tbar_pos_labels,
     _("Default position & visibility of the full-screen toolbar"),
     NULL, 0, NULL, NULL);

  xine_config_register_bool
    (this, "gui.windowedmode_separate_toolbar", 0,
     _("In windowed mode, whether the toolbar is in a separate window"), NULL,
     0, NULL, NULL);

  xine_config_register_bool
    (this, "gui.windowedmode_unblank", 0,
     _("In windowed mode, prevent blanking when playing video"), NULL,
     0, NULL, NULL);

  xine_config_register_bool
    (this, "gui.show_splash", 1,
     _("Display splash screen"),
     _("If enabled, gxine will display its splash screen"),
     0, NULL, NULL);
}

static int ui_listener (void *data, se_t *se, se_o_t *o, se_prop_t *prop,
			se_prop_read_t value)
{
  int i = (ui_adjustable_t *)data - ranges;
  if (value.i < ranges[i].min)
    value.i = ranges[i].min;
  else if (value.i > ranges[i].max)
    value.i = ranges[i].max;
  ui_set_control_adjustment (i, value.i);
  ui_xine_set_param_from_adjustment (i);
  return 0;
}

static GtkIconSource *make_icon_source (const char *name, GtkStateType state,
					GtkIconSize size)
{
  GtkIconSource *isrc = gtk_icon_source_new ();
  char *filename = *name == '/'
		   ? g_build_filename (icondir, name, NULL)
		   : g_build_filename (pixmapdir, name, NULL);
  gtk_icon_source_set_filename (isrc, filename);
  free (filename);
  gtk_icon_source_set_direction_wildcarded (isrc, TRUE);
  gtk_icon_source_set_size_wildcarded (isrc, (int) size == -1);
  if ((int) size != -1)
    gtk_icon_source_set_size (isrc, size);
  gtk_icon_source_set_state_wildcarded (isrc, (int) state == -1);
  if ((int) state != -1)
    gtk_icon_source_set_state (isrc, state);
  return isrc;
}

static GtkIconSet *ui_std_icon (GtkIconFactory *ifactory, const char *name,
				const char *file)
{
  GtkStockItem stock;
  if (!gtk_stock_lookup (name, &stock))
  {
    GtkIconSet *iset = gtk_icon_set_new ();
    gtk_icon_set_add_source (iset, make_icon_source (file, -1, -1));
    gtk_icon_factory_add (ifactory, name, iset);
    return iset;
  }
  return NULL;
}

static void ui_load_icons (void)
{
  GtkIconFactory *ifactory = gtk_icon_factory_new ();

  ui_std_icon (ifactory, GXINE_MEDIA_SPEAKER, "speaker.png");
  ui_std_icon (ifactory, GXINE_MEDIA_SPEAKER_MUTE, "nospeaker.png");
  ui_std_icon (ifactory, GXINE_MEDIA_MARK, "/gxine.png");
  ui_std_icon (ifactory, GXINE_LOGO, "/gxine.png");
//  ui_std_icon (ifactory, "gxine-settings-av-sync", "set-sync.svg");
//  ui_std_icon (ifactory, "gxine-settings-hue", "set-hue.svg");
  ui_std_icon (ifactory, "gxine-settings-saturation", "set-saturation.svg");
  ui_std_icon (ifactory, "gxine-settings-contrast", "set-contrast.svg");
  ui_std_icon (ifactory, "gxine-settings-brightness", "set-brightness.svg");
  ui_std_icon (ifactory, "gxine-settings-volume", "set-volume.svg");
//  ui_std_icon (ifactory, "gxine-settings-compressor", "set-compressor.svg");
//  ui_std_icon (ifactory, "gxine-settings-amplifier", "set-amplifier.svg");
#ifdef WITH_DEPRECATED
  ui_std_icon (ifactory, "gxine-logo", "/gxine.png");
#endif

  icon_size_logo = gtk_icon_size_register ("gxine-logo-size", 64, 48);

  gtk_icon_factory_add_default (ifactory);
}

/* GtkEntry clipboard paste and button press overrides */

static void (*old_gtk_entry_paste) (GtkEntry *, gpointer);
static gint (*old_gtk_entry_click) (GtkWidget *, GdkEventButton *, gpointer);

static void ui_paste_clipboard_cb (GtkEntry *entry, gpointer data)
{
  if (entry->editable)
  {
    GtkEditable *edit = GTK_EDITABLE (entry);
    play_item_t *item = clip_get_play_item ();
    if (item)
    {
      gint pos = gtk_editable_get_position (edit);
      char *text = g_object_get_data (G_OBJECT (entry), "mrl_component");
      switch (text ? *text : 0)
      {
      default:
	text = item->mrl;
	break;
      case 'N':
	text = item->title;
	break;
      case 'T':
	text = alloca (16);
	int_to_timestring (item->start_time, text, 16);
	gtk_editable_delete_text (edit, 0, -1);
	break;
      }
      gtk_editable_insert_text (edit, text, strlen (text), &pos);
      play_item_dispose (item);
    }
    else
      old_gtk_entry_paste (entry, data);
  }
}

static gint ui_click_clipboard_cb (GtkWidget *widget, GdkEventButton *event,
				   gpointer data)
{
  GtkEntry *entry = (GtkEntry *) widget;

  /* we need the same checks as GtkEntry's own button-press code */
  if (event->window != entry->text_area ||
      (entry->button && event->button != entry->button))
    return FALSE;

  /* do our own handling of middle-button clicks */
  if (event->button == 2 && event->type == GDK_BUTTON_PRESS && entry->editable)
  {
    GtkEditable *edit = GTK_EDITABLE (entry);
    play_item_t *item = clip_get_play_item ();
    if (item)
    {
      gint pos = gtk_editable_get_position (edit);
      gtk_editable_insert_text (edit, item->mrl, strlen (item->mrl), &pos);
      play_item_dispose (item);
      entry->button = event->button; /* GtkEntry does this, so we do too */
      return TRUE;
    }
    /* if we reach here, nothing has been pasted... */
  }

  return old_gtk_entry_click (widget, event, data);
}

/* UI code init */

void ui_init (void)
{
  xine_cfg_entry_t entry;
  unsigned int i;
  se_o_t *toolbars;

  static const ui_property_t tb_props[] = {
    { "fs", N_("v=bool, toggle(): full-screen toolbar visibility"), 0, get_fs_toolbar, set_fs_toolbar },
    { "wm", N_("v=bool, toggle(): windowed-mode toolbar visibility"), 0, get_wm_toolbar, set_wm_toolbar },
    { NULL }
  };
  static const ui_property_t tb_fs_props[] = {
    { "at_top", N_("v=bool, toggle(): full-screen toolbar position"), 0, get_fs_toolbar_top, set_fs_toolbar_top },
    { NULL }
  };
  static const ui_property_t tb_wm_props[] = {
    { "snap", N_("v=bool, toggle(): windowed-mode toolbar attachment"), 0, get_wm_toolbar_snap, set_wm_toolbar_snap },
    { NULL }
  };

#ifdef WITH_DEPRECATED
  static const se_f_def_t defs[] = {
    { "toolbar_show", js_fs_toolbar_show, 0, 0,
      SE_GROUP_HIDDEN, N_("[bool]"), NULL },
    { "set_toolbar_position", js_fs_toolbar_position, 0, 0,
      SE_GROUP_HIDDEN, N_("bool"), N_("at top if true") },
    { NULL }
  };

  se_defuns (gse, NULL, defs);
#endif

  /* Intercept GtkEntry's clipboard paste and button press code.
   * This should be done via events, but the paste event handler can't return a
   * value and both are useful for all GtkEntry widgets (but are only needed
   * for a couple of them).
   */
  {
    GtkEntryClass *klass = GTK_ENTRY_CLASS (g_type_class_peek (GTK_TYPE_ENTRY));
    GtkWidgetClass *wclass = (GtkWidgetClass *) klass;
    old_gtk_entry_paste = klass->paste_clipboard;
    klass->paste_clipboard = ui_paste_clipboard_cb;
    old_gtk_entry_click = wclass->button_press_event;
    wclass->button_press_event = ui_click_clipboard_cb;
  }

  toolbars = se_create_object (gse, NULL, "toolbar", NULL,
			       SE_GROUP_PROPERTIES, NULL);

  ui_create_properties (tb_props, toolbars, SE_TYPE_BOOL);
  ui_create_properties (tb_fs_props, se_find_object (gse, toolbars, "fs"),
			SE_TYPE_BOOL);
  ui_create_properties (tb_wm_props, se_find_object (gse, toolbars, "wm"),
			SE_TYPE_BOOL);
  ui_load_icons ();

  if (xine_config_lookup_entry (xine, "gui.fullscreen_toolbar", &entry))
  {
    fs_toolbar_at_top = !(entry.num_value & 1);
    fs_toolbar_visible = (entry.num_value >> 1) & 1;
  }

  for (i = 0; i < G_N_ELEMENTS (ranges); ++i)
  {
    if (ranges[i].setting)
    {
      static const se_f_def_t defs[] = {
	{ "revert", js_control_revert, 0, 0, SE_GROUP_HIDDEN, NULL, NULL },
	{ "reset", js_control_reset, 0, 0, SE_GROUP_HIDDEN, NULL, NULL },
	{ NULL }
      };
      jsobjs[i] =
	se_create_object (gse, NULL, ranges[i].jspref,
			  NULL, SE_GROUP_PROPERTIES,
			  "v=int, min, max; revert(), reset()");
      se_defuns (gse, jsobjs[i], defs);
      se_prop_create_xine_param (gse, jsobjs[i], "v",
				 ranges[i].param, SE_TYPE_INT);
      se_prop_add_listener (gse, jsobjs[i], "v", ui_listener, &ranges[i]);
      se_prop_create_int (gse, jsobjs[i], "min", ranges[i].min, TRUE);
      se_prop_create_int (gse, jsobjs[i], "max", ranges[i].max, TRUE);
    }
  }
}

gboolean ui_post_init (void)
{
  unsigned int i;
  gdk_threads_enter ();
  for (i = 0; i < G_N_ELEMENTS (ranges); ++i)
    if (ranges[i].param && i != Control_VOLUME)
      xine_set_param (stream, ranges[i].param, starts[i]);
  gdk_threads_leave ();
  return FALSE;
}

/* Undo button */

static void key_undo_cb (GtkAccelGroup *accel, GObject *obj, guint keyval,
			 GdkModifierType state)
{
  gtk_dialog_response (GTK_DIALOG (obj), GTK_RESPONSE_REJECT);
}

GtkAccelGroup *ui_add_undo_response (GtkWidget *window, GtkAccelGroup *accel)
{
  if (!accel)
  {
    accel = gtk_accel_group_new ();
    gtk_window_add_accel_group (GTK_WINDOW (window), accel);
  }
  gtk_accel_group_connect (accel, GDK_Undo, 0, 1,
			   g_cclosure_new_object (G_CALLBACK (key_undo_cb),
						  G_OBJECT (window)));
  return accel;
}
